﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Trinity.Configuration;
using Trinity.Core;
using System.IO;
using Trinity.Core.Lib;
using System.Collections;
using Trinity.Storage;

namespace SequenceAssembler
{
	enum StatusEnum {
		Untrusted = 0, Trusted, Connected, Complete
	}

	class Arc: Storable, FixedSize {
		public long Destination { get; set; }
		public short Multiplicity {get; set;}

		public int GetBinarySize() {
			return sizeof(long) + sizeof(short);
		}

		public void WriteBinary(BinaryWriter bw) {
			bw.Write(Destination);
			bw.Write(Multiplicity);
		}

		public void ReadBinary(BinaryReader br) {
			Destination = br.ReadInt64();
			Multiplicity = br.ReadInt16();
		}
	}

	class ReadOffset: Storable, FixedSize, IComparable<ReadOffset> {
		public int ReadId { get; set;}
		public int Offset { get; set;}

        public ReadOffset()
        {
        }

        public ReadOffset(int readId, int offset)
        {
            ReadId = readId;
            Offset = offset;
        }

		public int GetBinarySize()
		{
			return sizeof(int) + sizeof(int);
		}

		public void WriteBinary(BinaryWriter bw)
		{
			bw.Write(ReadId);
			bw.Write(Offset);
		}

		public void ReadBinary(BinaryReader br)
		{
			ReadId = br.ReadInt32();
			Offset = br.ReadInt32();
		}

		public int CompareTo(ReadOffset other) {
			if (this.ReadId < other.ReadId)
			{
				return -1;
			}
			else if (this.ReadId > other.ReadId)
			{
				return 1;
			}
			else
			{
				return this.Offset > other.Offset ? 1 : this.Offset == other.Offset ? 0 : -1;
			}
		}
	}

	/// <summary>
	/// Just build up kmers and prune untrusted
	/// After pruning each trusted cell should has a trusted twin
	/// </summary>
	class KmerCell : StorableCell
	{
		public long Id { get; set; }
		public byte Status { get; protected set;}
		public List<ReadOffset> ReadInfo { get; private set; }
		public float Coverage { get { return ReadInfo.Count; } }

		public KmerCell(): this(0L) {}

		public KmerCell(long Id) {
			this.Id = Id;
			Status = (byte) StatusEnum.Untrusted; //untrusted
			ReadInfo = new List<ReadOffset>();
		}

        public KmerCell(long Id, List<ReadOffset> readInfo)
        {
            this.Id = Id;
            Status = (byte)StatusEnum.Untrusted;
            ReadInfo = readInfo;
        }

		/// <summary>
		/// Not only change its Status as Trusted, but also sort its ReadInfo
		/// </summary>
		/// <returns></returns>
		public KmerCell TrustedCell() {
			this.Status = (byte)StatusEnum.Trusted; //trusted
			ReadInfo.Sort();
			return this;
		}

		public void AddReadOffset(int readId, int offset) {
			ReadOffset ro = new ReadOffset();
			ro.ReadId = readId;
			ro.Offset = offset;
			ReadInfo.Add(ro);
		}

        public void AddReadOffsetRange(List<ReadOffset> roList)
        {
            ReadInfo.AddRange(roList);
        }

		public int GetBinarySize()
		{
			int size = 0;
			size += sizeof(short); //Status
			size += sizeof(int); //ReadInfo.Count
			if (ReadInfo.Count != 0)
			{
				size += ReadInfo[0].GetBinarySize() * ReadInfo.Count; //ReadInfo
			}
			return size;
		}

		public void WriteBinary(BinaryWriter bw) {
			//Debug.Assert(Id != 0);
			bw.Write(Status);
			bw.Write(ReadInfo.Count);
			foreach (var readoffset in ReadInfo)
			{
				readoffset.WriteBinary(bw);
			}
		}

		public void ReadBinary(BinaryReader br) {
			Status = br.ReadByte();
			int cnt = br.ReadInt32();
			for ( int i  = 0; i < cnt; ++i) {
				var readoffset = new ReadOffset();
				readoffset.ReadBinary(br);
				ReadInfo.Add(readoffset);
			}
		}
	}

	///// <summary>
	///// Trusted kmers should be connected together
	///// </summary>
	//class ConnectedKmerCell : StorableCell
	//{
	//    public long Id { get; set; }
	//    public byte Status { get; protected set; }
	//    public List<ReadOffset> ReadInfo { get; private set; }
	//    public List<Arc> Arcs { get; private set; }
	//    public float Coverage { get { return ReadInfo.Count; } }

	//    public ConnectedKmerCell(): this(0L) {}

	//    public ConnectedKmerCell(long Id) {
	//        this.Id = Id;
	//        Status = (byte)StatusEnum.Trusted; //Trusted before Connected
	//        ReadInfo = new List<ReadOffset>();
	//        Arcs = new List<Arc>();
	//    }

	//    public int GetBinarySize()
	//    {
	//        int size = 0;
	//        size += sizeof(byte); //Status
	//        size += sizeof(int); //ReadInfo.Count
	//        if (ReadInfo.Count != 0)
	//        {
	//            size += ReadInfo[0].GetBinarySize() * ReadInfo.Count; //ReadInfo
	//        }
	//        size += sizeof(int); //Arcs.Count
	//        if (Arcs.Count != 0) {
	//            size += Arcs[0].GetBinarySize() * Arcs.Count;
	//        }
	//        return size;
	//    }

	//    public void WriteBinary(BinaryWriter bw) {
	//        Debug.Assert(Id != 0);
	//        bw.Write(Status);
	//        bw.Write(ReadInfo.Count);
	//        foreach (var readoffset in ReadInfo)
	//        {
	//            readoffset.WriteBinary(bw);
	//        }
	//        bw.Write(Arcs.Count);
	//        foreach (var arc in Arcs)
	//        {
	//            arc.WriteBinary(bw);
	//        }
	//    }

	//    public void ReadBinary(BinaryReader br) {
	//        Status = br.ReadByte();
	//        int cnt = br.ReadInt32();
	//        for ( int i  = 0; i < cnt; ++i) {
	//            var readoffset = new ReadOffset();
	//            readoffset.ReadBinary(br);
	//            ReadInfo.Add(readoffset);
	//        }
	//        cnt = br.ReadInt32();
	//        for (int i = 0; i < cnt; ++i) {
	//            var arc = new Arc();
	//            arc.ReadBinary(br);
	//            Arcs.Add(arc);
	//        }
	//    }

	//    public void ConnectTo(ConnectedKmerCell destination, short multiplicity) {
	//        Arc arc = new Arc();
	//        arc.Destination = destination.Id;
	//        arc.Multiplicity = multiplicity;
	//        Arcs.Add(arc);
	//    }
	//}

	/// <summary>
	/// Connected kmers will be merged to SequenceCell if possible.
	/// After merging, all kmers should be converted to SequenceCell
	/// even if they are not mergeable.
	/// Since after merge the bases it presents cannot be computed 
	/// from Id anymore, it requires Data field.
	/// ReadInfo now does not alway reflect Coverage.
	/// Since we choose Id to keep carefully, TwinId is always computable
	/// from Id and we do not need to store it explicitly.
	/// Visited is reserved for traversal algorithms.
	/// </summary>
	class SimpleSequenceCell : StorableCell
	{
		public long Id { get; set; }
		public byte Status { get; protected set; }
		public List<ReadOffset> ReadInfo { get; set; }
		public List<Arc> Arcs { get; set; }
		public float Coverage { get; set; }
		//public short Visited { get; set; }
		public BitDeque Data { get; private set; }
		public uint Size { get { return Data.size(); } }

		public SimpleSequenceCell(): this(0L) {}

		public SimpleSequenceCell(long id)
		{
			this.Id = id;
			Status = (byte)StatusEnum.Connected; //Connected
			Data = new BitDeque();
			Arcs = new List<Arc>();
			ReadInfo = new List<ReadOffset>();
			//Visited = 0;
		}

		public void ConnectTo(SequenceCell destination, short multiplicity)
		{
			ConnectTo(destination.Id, multiplicity);
		}

		public void ConnectTo(long destination, short multiplicity)
		{
			Arc arc = new Arc();
			arc.Destination = destination;
			arc.Multiplicity = multiplicity;
			Arcs.Add(arc);
		}

		public SimpleSequenceCell CompleteCell() {
			this.Status = (byte)StatusEnum.Complete;
			return this;
		}

		public int GetBinarySize()
		{
			int size = 0;
			size += sizeof(byte); //Status
			size += sizeof(int); //ReadInfo.Count
			if (ReadInfo.Count != 0) { //ReadInfo
				size += ReadInfo[0].GetBinarySize() * ReadInfo.Count;
			}
			size += sizeof(int); //Arcs.Count
			if (Arcs.Count != 0) //Arcs
			{
				size += Arcs[0].GetBinarySize() * Arcs.Count;
			}
			size += sizeof(float); //Coverage
			//size += sizeof(short); //Visited
			size += Data.GetBinarySize(); //Data
			return size;
		}

		public void WriteBinary(BinaryWriter bw) {
			Debug.Assert(Id != 0);
			bw.Write(Status);
			bw.Write(ReadInfo.Count);
			foreach (var readoffset in ReadInfo)
			{
				readoffset.WriteBinary(bw);
			}
			bw.Write(Arcs.Count);
			foreach (var arc in Arcs)
			{
				arc.WriteBinary(bw);
			}
			bw.Write(Coverage);
			//bw.Write(Visited);
			Data.WriteBinary(bw);
		}

		public void ReadBinary(BinaryReader br) {
			Status = br.ReadByte();
			int cnt = br.ReadInt32();
			for (int i = 0; i < cnt; ++i)
			{
				var readoffset = new ReadOffset();
				readoffset.ReadBinary(br);
				ReadInfo.Add(readoffset);
			}
			cnt = br.ReadInt32();
			for (int i = 0; i < cnt; ++i) {
				var arc = new Arc();
				arc.ReadBinary(br);
				Arcs.Add(arc);
			}
			Coverage = br.ReadSingle();
			//Visited = br.ReadInt16();
			Data.ReadBinary(br);
		}

		public SequenceCell NoArcCopy()
		{
			var clone = new SequenceCell(Id);
			clone.Coverage = Coverage;
			clone.Data.push_back_all(Data);
			clone.ReadInfo.AddRange(ReadInfo);
			clone.Status = Status;
			//clone.Visited = Visited;
			return clone;
		}
	}

	class InsertLibrary
	{
		public int LowerId{ get; private set; }
		public int Length { get; private set; }
		public double Variance { get; private set; }

		public InsertLibrary(int lowerId, int length, double variance)
		{
			this.LowerId = lowerId;
			this.Length = length;
			this.Variance = variance;
		}
	}

    class BackGraph: IDisposable
    {
        //for debug load save inconsistent
        //private BinaryWriter BugWriter = new BinaryWriter(new FileStream("bug.txt", FileMode.Create, FileAccess.Write));
        //private int BugCellNumber = 0;

        //public void CloseBugWriter()
        //{
        //    BugWriter.Close();
        //}

		public static byte[] ToBinary(StorableCell cell)
		{
			int bufferSize = cell.GetBinarySize();
			byte[] buffer = new byte[bufferSize];
			MemoryStream ms = new MemoryStream(buffer, true);
			using (BinaryWriter bw = new BinaryWriter(ms))
			{
				cell.WriteBinary(bw);
			}
			return buffer;
		}

		public static T FromBinary<T>(byte[] buffer, T cell = null) where T : class, StorableCell, new()
		{
			if (cell == null)
			{
				cell = new T();
			}
			MemoryStream ms = new MemoryStream(buffer, false);
			using (BinaryReader br = new BinaryReader(ms))
			{
				cell.ReadBinary(br);
			}
			return cell;
		}

		public long TwinId(long cellId)
		{
			long id = ~cellId;
			long twinId = 0L;
			for (int i = 0; i < KmerLen; ++i) {
				twinId = (twinId << 2) | (id & 3L);
				id >>= 2;
			}
			return twinId;
		}

        LocalMemoryPool GraphStorage;
		public short KmerLen { get; private set; }
		public List<InsertLibrary> InsertLengths { get; private set; }
		public int Categories { get { return InsertLengths.Count; } }

        public BackGraph(short kmerLen = 25)
        {
			this.KmerLen = kmerLen;
			this.InsertLengths = new List<InsertLibrary>();
			Config();
			GraphStorage = Global.LocalStorage;
        }

		private void Config()
		{
			TrinityConfig.CurrentRunningMode = RunningMode.Embedded;
            TrinityConfig.MemoryPoolSize = 1024L * 1024 * 1024 * 16;
		}

		public void Dispose()
		{
			Save();
		}
		
		public bool Load() {
			return GraphStorage.LoadStorage();
		}

		public bool Save() {
			return GraphStorage.SaveStorage();
		}

		public bool Reset() {
			return GraphStorage.ResetStorage();
		}

		public int AddCategory(int lowerId, int len, double var)
		{
			InsertLengths.Add(new InsertLibrary(lowerId, len, var));
			return Categories - 1;
		}

		public void SaveCells(StorableCell[] cells) {
			long[] ids = new long[cells.Length];
			byte[][] datas = new byte[cells.Length][];
			for (int i = 0; i < cells.Length; ++i) {
				ids[i] = cells[i].Id;
				datas[i] = ToBinary(cells[i]);

                 #region for debug load save inconsistent
                //BugCellNumber += 1;
                //BugWriter.Write(ids[i]);
                //BugWriter.Write(datas[i].Length);
                //BugWriter.Write(datas[i]);
                #endregion
            }
			GraphStorage.BatchCellBytesSave(ids, datas);
		}

		public void SaveCell(StorableCell c)
        {
			GraphStorage.SaveCellBytes(c.Id, ToBinary(c));
        }

		public T[] LoadCells<T>(long[] ids) where T : class, StorableCell, new()
		{
			byte[][] cellsBytes = GraphStorage.BatchCellBytesLoad(ids);
			T[] cs = new T[ids.Length];
			for (int i = 0; i < ids.Length; ++i)
			{
                try
                {
                    if (cellsBytes[i].Length != 0)
                    {
                        cs[i] = FromBinary<T>(cellsBytes[i]);
                        cs[i].Id = ids[i];
                    }
                    else
                    {
                        cs[i] = null;
                    }
                }
                catch (Exception e)
                {
                    //Console.WriteLine("bug cell id = " + ids[i]);
                    //Console.WriteLine("bug cell number = " + BugCellNumber);
                    //BugWriter.Close();
                    throw e;
                }
			}
			return cs;
		}

		public T LoadCell<T>(long id, StatusEnum status) where T : class, StorableCell, new()
		{
			byte[] cellBytes = GraphStorage.LoadCellBytes(id);
			if (cellBytes.Length == 0 || cellBytes[0] != (byte)status)
			{
				return null;
			}
			T c = FromBinary<T>(cellBytes);
			c.Id = id;
			return c;
		}

		public T LoadCell<T>(long id) where T : class, StorableCell, new()
        {
			byte[] cellBytes = GraphStorage.LoadCellBytes(id);
			if (cellBytes.Length == 0)
			{
				return null;
			}
            T c = FromBinary<T>(cellBytes);
			c.Id = id;
            return c;
        }

		public bool DeleteCell(StorableCell c)
		{
			return GraphStorage.RemoveCellBytes(c.Id);
		}

		public long CellCount {	get { return GraphStorage.CellCount; } }

		public List<long> CellList { get { return GraphStorage.CellList; } }

		public static void unitTestReset() {
			using (BackGraph bg = new BackGraph(25)) {
				Console.WriteLine("reset graph and add new cells");
				KmerCell cell = new KmerCell(1);
				bg.SaveCell(cell);
				cell = new KmerCell(2);
				bg.SaveCell(cell);
				cell = new KmerCell(3);
				bg.SaveCell(cell);
				foreach (var e in bg.CellList)
				{
					Console.Write(e);
					Console.Write(',');
				}
				Console.WriteLine();
				bg.Save();
			}
		}

		public static void unitTestLoad()
		{
			using (BackGraph bg = new BackGraph(25))
			{
				bg.Load();
				Console.WriteLine("load graph");
				Console.WriteLine("cell count: {0}", bg.CellCount);
				foreach (var e in bg.CellList)
				{
					Console.Write(e);
					Console.Write(',');
				}
				Console.WriteLine();
				KmerCell cell = bg.LoadCell<KmerCell>(1);
				Console.WriteLine(cell.Id);
				cell = bg.LoadCell<KmerCell>(10);
				if (cell != null)
				{
					Console.WriteLine(cell.Id);
				} else {
					Console.WriteLine("cell {0} does not exist", 10);
				}
			}
		}

		public static void unitTestTwinId()
		{
			using (BackGraph bg = new BackGraph(25))
			{
				long id = 1L;
				long twinId = bg.TwinId(id);
				Console.WriteLine(Convert.ToString(id, 2));
				Console.WriteLine(Convert.ToString(twinId, 2));
				id = 2211334422L;
				twinId = bg.TwinId(id);
				Console.WriteLine(Convert.ToString(id, 2));
				Console.WriteLine(Convert.ToString(twinId, 2));
			}
		}
	}

    class Demons
    {

        public Demons()
        {
            Config();
        }
        private void Config()
        {
            TrinityConfig.CurrentRunningMode = RunningMode.Embedded;
        }

		//public void SimpleGraph()
		//{
		//    //MyGraph g = new MyGraph();
		//    //MyCell c1 = new MyCell(1);
		//    //MyCell c2 = new MyCell(2);
		//    //MyCell c3 = new MyCell(3);
		//    //g.AddEdge(c1, c2);
		//    //g.AddEdge(c2, c3);
		//    //g.AddEdge(c3, c1);

		//    //g.SaveCell(c1);
		//    //g.SaveCell(c2);
		//    //g.SaveCell(c3);

		//    //Global.LocalStorage.SaveStorage();
            
		//    Global.LocalStorage.LoadStorage();
		//    BackGraph g1 = new BackGraph();
		//    TranverseGraph(g1, 1);
		//}

		//private void TranverseGraph(BackGraph g, long id)
		//{
		//    Queue<long> q = new Queue<long>();
		//    HashSet<long> visited = new HashSet<long>();
		//    q.Enqueue(id);
		//    visited.Add(id);
            
		//    while (q.Count != 0)
		//    {
		//        SequenceCell c = g.LoadCell(q.Dequeue());
		//        Console.Write(c.ID + " ");
		//        foreach (Pair<long, short> p in c.Arcs)
		//        {
		//            if (visited.Contains(p.First) == false)
		//            {
		//                visited.Add(p.First);
		//                q.Enqueue(p.First);
		//            }
		//        }
		//    }
		//}
    }
}
